// 引入客户配置
// require('dotenv').config()
const axios = require('axios')
const { get, post } = require('./http')
// 引入爬虫模拟
const puppeteer = require('puppeteer-extra')
const UserAgent = require('user-agents')
const StealthPlugin = require('puppeteer-extra-plugin-stealth')
puppeteer.use(StealthPlugin())
// 引入短信
const accountSid = 'AC51bfe02735d4c266bf5ef35efaf9a79f'
const authToken = '4a644295cb375a82195d13f1c5ba68dc'
const client = require('twilio')(accountSid, authToken)
// 引入时间
const moment = require('moment')
//引入邮件模块
const nodemailer = require('nodemailer')
//设置邮箱配置
let transporter = nodemailer.createTransport({
  // 邮箱服务的主机
  host:'smtp.qq.com', 
  // 开启安全连接
  secure:false,
  // 用户信息
  auth:{
    user:'3154538973@qq.com',
    pass:'aouompufjrgydeci'
  }
})

require('console-stamp')(console, {
  metadata: () => '日志',
  colors: {
    stamp: 'yellow',
    label: 'white',
    metadata: 'green',
  }
})

// 请求客户配置信息
const cus = 'http://120.76.140.92:9001/api/options/getOptionsAll'
// 请求IP
const url = 'http://120.76.140.92:9001/api/ipl/getIplOne'
// 删除IP
const del = 'http://120.76.140.92:9001/api/ipl/delByIp'

// 当前客户配置信息数据集合
var customerList = []
var cusNumber = 0;
var cusLength = 0;
// 当前IP
var ip = ''

// 设置代理
const fetchProxy = async () => {
  try {
    // 请求IP
    let res = await axios.get(url)
    if(res.data === ''){
      // 为空等待10秒后请求
      console.log('等待代理……10s')
      sleep(10)
      res = await axios.get(url)
      // 返回值
      return res.data.data[0].t_ipl
    }
    // 存储IP
    ip = res.data.data[0].t_ipl
    //  设定代理IP
    const proxy = ip
    console.log(`[PROXY] IP值 -> ${ip === '' ? '[无]' : ip}`)
    console.log(`[CUSTOMER] 护照编号 -> ${customerList[cusNumber].t_no}`)
    // 删除资源池对应IP
    let params = { t_ipl: res.data.data[0].t_ipl }
    await post(del, params).then(res => {
      if(res.success) { console.log('删除资源池对应IP，避免重复使用！') }
    }).catch(err => {console.log(err)});
    // 记录代理IP消耗量
    let incSip = {
      t_no: customerList[cusNumber].t_no,
      t_create: moment().format('YYYY-MM-DD'),
      mark: 1
    }
    await post('http://120.76.140.92:9001/api/sums/incSip', incSip).then(res => {
      if(res.success) { console.log('记录代理IP消耗量 + 1') }
    }).catch(err => {console.log(err)});
    // 开始代理访问
    return proxy
  } catch (err) {
    // 打印报错
    console.log(err);
    // 重新开始
    run()
  }
}

// 延迟加载 - 公共方法
const sleep = (d) => {
  for(let t = Date.now();Date.now() - t <= d;);
}

// 有票&消息推送
const have = () => {
  // 设置收件人信息
  let mailOptions = {
    from:'3154538973@qq.com',
    to: customerList[cusNumber].t_email,
    subject:'中国南方航空机票 - 有票提醒',
    html:  `始发地-目的地-日期 客户名称：${customerList[cusNumber].t_name} <br/>`+
          `账户：[${customerList[cusNumber].AGENT_USERNAME}]<br/>` + 
          `密码：[${customerList[cusNumber].AGENT_PASSWORD}]<br/>` +
          `地址：https://b2c.csair.com/ita/intl/zh/flights?flex=1&m=0&p=100&t=${customerList[cusNumber].PASSENGER_FROM}-${customerList[cusNumber].PASSENGER_TO}-${customerList[cusNumber].PASSENGER_DATE}&egs=ITA,ITA&open=1&flex=1&nb=1`
  }
  // 发送邮件
  transporter.sendMail(mailOptions,(error,info)=>{
    if(error)
      return console.log(error)
      console.log(`邮件信息: ${info.messageId}`)
      console.log(`发送: ${info.response}`)
  })
}

// 下单&消息推送
const notify = e => {
  const body = e == 'ready' ? `${metadata} 准备付款购买 [${customerList[cusNumber].AGENT_USERNAME}](${customerList[cusNumber].AGENT_PASSWORD})` : `${metadata} 预订票 [${customerList[cusNumber].AGENT_USERNAME}](${customerList[cusNumber].AGENT_PASSWORD})`
  // 设置收件人信息
  let mailOptions = {
    from:'3154538973@qq.com',
    to: customerList[cusNumber].t_email,
    subject:'中国南方航空机票 - 预约成功',
    html:  `始发地-目的地-日期 客户名称：${metadata} <br/>`+
          `账户：[${customerList[cusNumber].AGENT_USERNAME}]<br/>` + 
          `密码：[${customerList[cusNumber].AGENT_PASSWORD}]`
  }
  // 发送邮件
  transporter.sendMail(mailOptions,(error,info)=>{
    if(error)
      return console.log(error)
      console.log(`邮件信息: ${info.messageId}`)
      console.log(`发送: ${info.response}`)
  })

  // 推送短信
  if(customerList[cusNumber].t_tell.indexOf(',')){
    let tells = customerList[cusNumber].t_tell.split(',')
    for (let i = tells.length; i--;){
      client.messages.create({
        body,
        from: '+15108718159',
        to: '+' + tells[i]
      })
    }
  } else {
    client.messages.create({
      body,
      from: '+15108718159',
      to: '+' + customerList[cusNumber].t_tell
    })
  }
}

// 初始化浏览器
const initBrowser = async (proxy) => {
  const options = {
    // slowMo: 100,
    headless: true,
    ignoreHTTPSErrors: true,
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-infobars',
      '--window-position=0,0',
      '--ignore-certifcate-errors',
      '--ignore-certifcate-errors-spki-list',
      `--proxy-server=${proxy}`
    ]
  }
  const browser = await puppeteer.launch(options)
  return browser
}

// 初始化页面
const setupPage = async (page) => {
  const userAgent = new UserAgent()
  await page.setUserAgent(String(userAgent.data.userAgent))
  const currentAgent = await page.evaluate('navigator.userAgent')
}

// 查询
const search = async page => {
  console.log('[SEARCH] 开始查询')
  const url = `https://b2c.csair.com/ita/intl/zh/flights?flex=1&m=0&p=100&t=${customerList[cusNumber].PASSENGER_FROM}-${customerList[cusNumber].PASSENGER_TO}-${customerList[cusNumber].PASSENGER_DATE}&egs=ITA,ITA&open=1&flex=1&nb=1`
  await page.goto(url)
  
  // 记录请求数
  let incSum = {
    t_no: customerList[cusNumber].t_no,
    t_create: moment().format('YYYY-MM-DD'),
    mark: 1
  }
  await post('http://120.76.140.92:9001/api/sums/incSum', incSum).then(res => {
    if(res.success) { console.log('记录请求数 + 1') }
  }).catch(err => {console.log(err)});
  
  const TRIP = '#sh-trip-item-0-0'
  const BUY = '#cabin_0_0_0'
  const SOLD_OUT = '#sh-trip-item-0-0 > div.sh-cabins > div.cell.cab-0.sold-out'
  const SEARCH = '#bk-form-submit'
  const ECONOMY = '.cab-0'

  while (true) {
    console.log('[SEARCH] 验证URL')
    if (!page.url().includes('execution') && page.url().includes('modules')) throw new Error(`进入虚拟页面 -> ${page.url()}`)
    console.log(`[SEARCH] 有效URL -> ${page.url()}`)
    console.log('[SEARCH] 加载机票信息')

    // 过滤重试机制
    let ERR = await page.$(TRIP)
    if (ERR === null) {
      await page.reload()
      console.log('[PAGE] 重新加载页面')
      // 记录请求数
      let incSum = {
        t_no: customerList[cusNumber].t_no,
        t_create: moment().format('YYYY-MM-DD'),
        mark: 1
      }
      await post('http://120.76.140.92:9001/api/sums/incSum', incSum).then(res => {
        if(res.success) { console.log('记录请求数 + 1') }
      }).catch(err => {console.log(err)});
      ERR = await page.$(TRIP)
      return ERR
    } else {
      await page.waitForSelector(TRIP)
    }
    console.log('[SEARCH] 加载查询结果')
    console.log('[SEARCH] 筛选机票类型')
    if(customerList[cusNumber].PASSENGER_CLASS === '默认舱'){
      if ((await page.$(BUY)) !== null) {
        found = true
        await page.screenshot({
          path: `screenshots/${new Date().toISOString()}.png`,
          fullPage: true,
        })
        await have()
        await page.click(BUY)
        console.info(`捕获目标`)
        console.log('[SEARCH] 目标机票查询完成')
        
        // 记录查询成功
        let incSearch = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incSearch', incSearch).then(res => {
          if(res.success) { console.log('记录查询成功 + 1') }
        }).catch(err => {console.log(err)});

        break
      } else {
        if ((await page.$(SOLD_OUT)) === null) throw new Error(`进入虚拟页面 -> ${page.url()}`)
        console.log('[SEARCH] 已售罄 重新查询')

        // 记录查询售罄
        let incOver = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incOver', incOver).then(res => {
          if(res.success) { console.log('记录查询售罄 + 1') }
        }).catch(err => {console.log(err)});

        await page.click(SEARCH)
        await page.waitForNavigation()
      }
    } else {
      const bodyHandle = await page.$(ECONOMY)
      const html = await page.evaluate(body => body.innerHTML, bodyHandle)
      const EconomyFlag = html.indexOf(customerList[cusNumber].PASSENGER_CLASS) != -1
      if ((await page.$(BUY)) !== null && EconomyFlag) {
        found = true
        await page.screenshot({
          path: `screenshots/${new Date().toISOString()}.png`,
          fullPage: true,
        })
        await have()
        await page.click(BUY)
        console.info(`捕获目标`)
        console.log('[SEARCH] 目标机票查询完成')

         // 记录查询成功
         let incSearch = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incSearch', incSearch).then(res => {
          if(res.success) { console.log('记录查询成功 + 1') }
        }).catch(err => {console.log(err)});

        break
      } else {
        if ((await page.$(SOLD_OUT)) === null) throw new Error(`进入虚拟页面 -> ${page.url()}`)
        console.log('[SEARCH] 已售罄 重新查询')

         // 记录查询售罄
         let incOver = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incOver', incOver).then(res => {
          if(res.success) { console.log('记录查询售罄 + 1') }
        }).catch(err => {console.log(err)});

        await page.click(SEARCH)
        await page.waitForNavigation()
      }
    }
  }
}
// 登录
const login = async page => {
  const USER = '#loginForm > div:nth-child(1) > div > input'
  const PASS = '#loginForm > div:nth-child(2) > div > input.ipt.u-pwd.greyfont._placeholder'
  const AGREE = '#loginForm > div.lg-gdprBox > div > input'
  const LOGIN = '#loginForm > div.form-action > button'

  console.log('[LOGIN] 加载登录')
  
  // 检索登录表单机制
  let LOGINFORM = '#cm-login-box'
  let ERR = await page.$(LOGINFORM)
  if (ERR === null) {
    let ONBTN = await page.$('#cabin_0_0_0')
    if(ONBTN){
      // 记录加载登录失败
      let incWlogo = {
        t_no: customerList[cusNumber].t_no,
        t_create: moment().format('YYYY-MM-DD'),
        mark: 1
      }
      await post('http://120.76.140.92:9001/api/sums/incWlogo', incWlogo).then(res => {
        if(res.success) { console.log('加载登录失败 + 1') }
      }).catch(err => {console.log(err)});

      await page.click('#cabin_0_0_0')
      console.log('[RELOGIN] 重新加载登录')
      return ERR
    } else {
      sleep(2000)
      console.log('[ERROR] 加载登录失败，重新加载')
       // 记录加载登录失败
       let incWlogo = {
        t_no: customerList[cusNumber].t_no,
        t_create: moment().format('YYYY-MM-DD'),
        mark: 1
      }
      await post('http://120.76.140.92:9001/api/sums/incWlogo', incWlogo).then(res => {
        if(res.success) { console.log('加载登录失败 + 1') }
      }).catch(err => {console.log(err)});
      run()
    }
  }
  await page.waitForSelector(USER)
  await page.waitForSelector(PASS)
  await page.waitForSelector(AGREE)
  console.log('[LOGIN] 登录加载完成')

  console.log('[LOGIN] 开始登录')
  await page.click(USER)
  await page.keyboard.type(agent.username)
  await page.click(PASS)
  await page.keyboard.type(agent.password)
  await page.click(AGREE)
  await page.click(LOGIN)
  console.log('[LOGIN] 登录完成')

  // 记录登录成功
  let incLogo = {
    t_no: customerList[cusNumber].t_no,
    t_create: moment().format('YYYY-MM-DD'),
    mark: 1
  }
  await post('http://120.76.140.92:9001/api/sums/incLogo', incLogo)
  
  await page.screenshot({
    path: `screenshots/${new Date().toISOString()}.png`,
    fullPage: true,
  })
}

// 购买预订
const buy = async page => {
  const LASTNAME = '.pg-cell > .ctrl-cell > .ipt-cell > .ipt-cell > .p-l-name'
  const FIRSTNAME = '.pg-cell > .ctrl-cell > .ipt-cell > .ipt-cell > .p-f-name'
  const PASSPORT = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-doc'
  const BIRTHDAY = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-birthday'
  const GENDER_M = '#__dom_id_0'
  const GENDER_F = '#__dom_id_1'
  const VALID = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-doc-expiry'
  const EMAIL = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-email'
  const PHONE1 = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-phone-main'
  const PHONE2 = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-phone-other'
  const ADDRESS = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-dc-addr'
  const CITY = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-dc-city'
  const POSTCODE = '.pg-block-body > .pg-cell > .ctrl-cell > .ipt-cell > .p-dc-zipCode'
  const AGENT_PHONE = '.ipt.c-phone.c-phone-number'
  const AGENT_EMAIL = '#contact-block > form > div:nth-child(2) > div:nth-child(1) > div.ipt-cell.email-box > input'

  const AGREE = '#pg-check-term'
  const BOOK = '#goNext'
  const BUY = '#payDiv > input'

  console.log('[ORDER] 加载客户信息表单')
  await page.waitForSelector(LASTNAME)
  await page.waitForSelector(PASSPORT)
  await page.waitForSelector(BIRTHDAY)
  await page.waitForSelector(BOOK)
  console.log('[ORDER] 客户信息表单加载完成')

  console.log('[ORDER] 填写乘客信息')
  await page.click(LASTNAME)
  await page.keyboard.type(customerList[cusNumber].PASSENGER_LASTNAME)
  await page.click(FIRSTNAME)
  await page.keyboard.type(customerList[cusNumber].PASSENGER_FIRSTNAME)
  if ((await page.$(GENDER_M)) !== null) {
    if (customerList[cusNumber].PASSENGER_GENDER === 'M' || customerList[cusNumber].PASSENGER_GENDER === 'm') await page.click(GENDER_M)
    if (customerList[cusNumber].PASSENGER_GENDER === 'F' || customerList[cusNumber].PASSENGER_GENDER === 'f') await page.click(GENDER_F)
  }
  await page.click(PASSPORT)
  await page.keyboard.type(customerList[cusNumber].PASSENGER_PASSPORT)
  await page.click(BIRTHDAY)
  await page.keyboard.type(customerList[cusNumber].PASSENGER_BIRTHDAY)
  if ((await page.$eval(PASSPORT, e => e.value)) == '') {
    await page.click(PASSPORT)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_PASSPORT)
  }
  if ((await page.$(VALID)) !== null) {
    await page.click(VALID)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_VALID)
  }
  if ((await page.$(PHONE1)) !== null) {
    await page.click(PHONE1)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_PHONE1)
  }
  if ((await page.$(EMAIL)) !== null) {
    await page.click(EMAIL)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_EMAIL)
  }
  if ((await page.$(PHONE2)) !== null) {
    await page.click(PHONE2)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_PHONE2)
  }
  if ((await page.$(ADDRESS)) !== null) {
    await page.click(ADDRESS)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_ADDRESS)
  }
  if ((await page.$(CITY)) !== null) {
    await page.click(CITY)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_CITY)
  }
  if ((await page.$(POSTCODE)) !== null) {
    await page.click(POSTCODE)
    await page.keyboard.type(customerList[cusNumber].PASSENGER_POSTCODE)
  }
  if ((await page.$eval(AGENT_PHONE, e => e.value)) == '') {
    await page.click(AGENT_PHONE)
    await page.keyboard.type(customerList[cusNumber].AGENT_PHONE)
  }
  if ((await page.$eval(AGENT_EMAIL, e => e.value)) == '') {
    await page.click(AGENT_EMAIL)
    await page.keyboard.type(customerList[cusNumber].AGENT_EMAIL)
  }
  await page.click(AGREE)
  console.log('[ORDER] 填写乘客信息完成')

  console.log('[ORDER] 预订机票')
  await page.click(BOOK)
  await page.screenshot({
    path: `screenshots/${new Date().toISOString()}.png`,
    fullPage: true,
  })
  notify('found')

  // 记录登录成功
  let incSuccess = {
    t_no: customerList[cusNumber].t_no,
    t_create: moment().format('YYYY-MM-DD'),
    mark: 1
  }
  await post('http://120.76.140.92:9001/api/sums/incSuccess', incSuccess)

  console.log('[ORDER] 确认预订')
  await page.waitFor(10000)
  await page.screenshot({
    path: `screenshots/${new Date().toISOString()}.png`,
    fullPage: true,
  })
  await page.waitForSelector(BUY)
  order = true
  console.info('确认中……')
  console.log('[ORDER] 订单确认')
}


// 程序启动运行
let order = false

const run = async () => {
  if (cusNumber === cusLength || cusNumber > cusLength) {
    // 请求客户
    let cusls = await axios.get(cus)
    // 存储客户配置信息
    customerList = cusls.data.data;
    // 记录客户数组长度，循环使用
    cusLength = customerList.length - 1;
    cusNumber = 0;
  } else {
    cusNumber++
  }
  let proxy = await fetchProxy()
  let browser = await initBrowser(proxy)
  while (order === false) {
    const page = await browser.newPage()
    try {
      await setupPage(page)
      await search(page)
      await login(page)
      await buy(page)
      notify('ready')
    } catch (err) {
      // 报错过滤机制
      let errType = err.message.substr(5, 27)
      console.log(errType)
      if(errType === 'is either not visible or no'){
        console.log('[ERROR] 无效查询')
        // 记录无效查询
        let incWsearch = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incWsearch', incWsearch).then(res => {
          if(res.success) { console.log('记录无效查询 + 1') }
        }).catch(err => {console.log(err)});
      }
      if(errType === 'ERR_EMPTY_RESPONSE at https'){
        console.log('[ERROR] 无效查询')
        // 记录无效查询
        let incWsearch = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incWsearch', incWsearch).then(res => {
          if(res.success) { console.log('记录无效查询 + 1') }
        }).catch(err => {console.log(err)});
      }
      if(errType === 'ERR_CONNECTION_RESET at htt'){
        console.log('[ERROR] 无效查询')
        // 记录无效查询
        let incWsearch = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incWsearch', incWsearch).then(res => {
          if(res.success) { console.log('记录无效查询 + 1') }
        }).catch(err => {console.log(err)});
      }
      if(errType === 'ERR_PROXY_CONNECTION_FAILED'){
        console.log('[ERROR] 无效IP代理')
          // 记录无效IP
          let incWip = {
            t_no: customerList[cusNumber].t_no,
            t_create: moment().format('YYYY-MM-DD'),
            mark: 1
          }
          await post('http://120.76.140.92:9001/api/sums/incWip', incWip).then(res => {
            if(res.success) { console.log('无效IP代理 + 1') }
          }).catch(err => {console.log(err)});
      }
      if(errType === '面 -> https://b2c.csair.com/'){
        console.log('[ERROR] 虚拟页面')
         // 记录虚拟页面
         let incXny = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incXny', incXny).then(res => {
          if(res.success) { console.log('虚拟页面 + 1') }
        }).catch(err => {console.log(err)});
      }
      if(errType === 'ERR_CONNECTION_CLOSED at ht'){
        console.log('[ERROR] 链接关闭')
        // 记录连接关闭
        let incClose = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incClose', incClose).then(res => {
          if(res.success) { console.log('记录连接关闭 + 1') }
        }).catch(err => {console.log(err)});
      }
      if(errType === 'ng for selector "#loginForm'){
        console.log('[ERROR] 无效登录页面')
        // 无效登录
        let incWlogo = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incWlogo', incWlogo).then(res => {
          if(res.success) { console.log('无效登录 + 1') }
        }).catch(err => {console.log(err)});
      }
      if(errType === 'ation timeout of 30000 ms e'){
        console.log('[ERROR] 请求超时')
        // 请求超时
        let incCtime = {
          t_no: customerList[cusNumber].t_no,
          t_create: moment().format('YYYY-MM-DD'),
          mark: 1
        }
        await post('http://120.76.140.92:9001/api/sums/incCtime', incCtime).then(res => {
          if(res.success) { console.log('请求超时 + 1') }
        }).catch(err => {console.log(err)});
      }
      console.log(err.message)
      await page.close()
      await browser.close()

      if (cusNumber === cusLength || cusNumber > cusLength) {
        // 请求客户
        let cusls = await axios.get(cus)
        // 存储客户配置信息
        customerList = cusls.data.data;
        // 记录客户数组长度，循环使用
        cusLength = customerList.length - 1;
        cusNumber = 0;
      } else {
        cusNumber++
      }
      proxy = await fetchProxy()
      browser = await initBrowser(proxy)
    }
  }
}

run()